package simplex;

import java.util.ArrayList;

import javax.swing.JTextArea;

public class Simplex {

	public static final int IMPOSSIBLE_DIV = -999;
	private String[] celNames;
	int artificialVariablesCount = 0;
	private ArrayList<double[]> ogr;
	private static int count = 0;
	private double[] cel;
	private int conditionNumber;
	private JTextArea logArea;
	private int[] exitCell = { -1, -1 };
	private boolean minimum;
	/**
	 * Flaga która sprawdza czy powinniśmy sprawdzić czy istnieją alternatywne
	 * rozwiązania
	 */
	private boolean checkAlternatives = true;
	/**
	 * Ilość alternatywnych rozwiązań
	 */
	private int alternativesCount = 0;
	/**
	 * indeks kolumny do rozwiązania alternatywnego;
	 */
	private ArrayList<Double> alternativeColumn;
	/**
	 * Wartość optymalna
	 */
	private double endValue;
	private double[][] simplexTable;
	/**
	 * Iloczyn skalarny zmiennych w bazie i ai
	 */
	private double[] zJ;
	/**
	 * różnica cel - zJ
	 */
	private double[] simplexCriterium;
	/**
	 * Numery i wartości zmiennych bazowych
	 */
	private double[][] baseValues;
	/**
	 * Wartości ograniczeń Bi oraz Bi/Aij
	 */
	private double[][] ogrValues;
	private boolean step;

	public Simplex(ArrayList<double[]> o, double[] c, boolean min,
			JTextArea lA, boolean s) {
		step = s;
		ogr = o;
		cel = c;
		conditionNumber = o.size();
		minimum = min;
		logArea = lA;
		checkConditions();
		celNames = new String[cel.length];
		alternativeColumn = new ArrayList<Double>();
	}

	public boolean firstSimplex() {
		if (exitCell[0] != -1) {
			logArea.append("\nPodmieniamy wartość bazową z "
					+ getCelNames()[(int) getBaseValues()[exitCell[1]][0]]
					+ " na " + getCelNames()[exitCell[0]]);
			changeBaseValues(exitCell[0], exitCell[1]);
			recalculateSimplexTableAndOgrValues(exitCell[0], exitCell[1]);
		}
		endValue = 0.0;
		resetZjAndSimplexCriterium();
		// Obliczanie zJ - iloczyn skalarny
		for (int j = 0; j < ogr.size(); j++) {
			for (int i = 0; i < cel.length; i++) {
				zJ[i] = zJ[i] + (simplexTable[j][i] * baseValues[j][1]);
			}
			endValue = endValue + (ogrValues[0][j] * baseValues[j][1]);
		}

		for (int i = 0; i < cel.length; i++) {
			simplexCriterium[i] = cel[i] - zJ[i];
		}

		if (isStop()) {
			// Sprawdzamy czy nie jest układ sprzeczny
			for (int i = 0; i < getBaseValues().length; i++) {
				if (getCelNames()[(int) getBaseValues()[i][0]].contains("S")) {
					throw new NullPointerException(
							"Układ sprzeczny - zmienna sztuczna ma wartość niezerową");
				}
			}
			// Alternatywne rozwiązania
			if (checkAlternatives) {
				checkAlternatives = false;
				double[] baseValuseIndexes = new double[baseValues.length];
				for (int i = 0; i < baseValues.length; i++) {
					baseValuseIndexes[i] = baseValues[i][0];
				}
				for (int i = 0; i < simplexCriterium.length; i++) {
					if (simplexCriterium[i] == 0) {
						boolean cond = true;
						for (int j = 0; j < baseValuseIndexes.length; j++) {
							if ((double) i == baseValuseIndexes[j]) {
								cond = false;
								break;
							}
						}
						if (cond) {
							alternativesCount++;
							alternativeColumn.add(new Double(i));
						}
					}
				}
			}
			logArea.append("\nZnaleziono rozwiązanie");
			logArea.append("\nWartości zmiennych:");
			for (int i = 0; i < getBaseValues().length; i++) {
				logArea.append("\n"
						+ getCelNames()[(int) getBaseValues()[i][0]] + " -> "
						+ getOgrValues()[0][i]);
			}
			logArea.append("\nWartość optymalna: " + endValue);

			if (alternativesCount != 0) {
				logArea.append("\nIstnieją " + String.valueOf(alternativesCount)
						+ " dodatkowe rozwiązania");
				alternativesCount--;
				exitCell[0] = alternativeColumn.get(0).intValue();
				exitCell[1] = calculateExitRow(exitCell[0]);
				alternativeColumn.remove(0);
				return false;
			}
			return true;
		} else {
			logArea.append("\n" + String.valueOf(++count)
					+ " tablica simpleksowa.\nNie ma rozwiązania.");
			exitCell[0] = getMaxValue(simplexCriterium);
			// Bi/Aik
			exitCell[1] = calculateExitRow(exitCell[0]);
			if (exitCell[1] == Simplex.IMPOSSIBLE_DIV) {
				throw new NullPointerException("Nieskończenie wiele rozwiązań");
			}

			logArea.append("\nKolumna: " + (exitCell[0] + 3) + " Wiersz: "
					+ (exitCell[1] + 3));
			logArea.append("\nWartość w tej komórce: "
					+ getValueAt(simplexTable, exitCell[1], exitCell[0]));
			if (step) {
				return false;
			} else {
				return firstSimplex();
			}

		}

	}

	private double getValueAt(double[][] array, int row, int col) {
		return array[row][col];
	}

	public void resetZjAndSimplexCriterium() {
		for (int i = 0; i < simplexCriterium.length; i++) {
			zJ[i] = 0.0;
			simplexCriterium[i] = 0.0;
		}
	}

	public void changeBaseValues(int enter, int exit) {
		baseValues[exit][0] = enter;
		baseValues[exit][1] = cel[enter];
	}

	public void recalculateSimplexTableAndOgrValues(int col, int row) {
		double div = simplexTable[row][col];
		for (int i = 0; i < cel.length; i++) {
			simplexTable[row][i] = simplexTable[row][i] / div;
		}
		ogrValues[0][row] = ogrValues[0][row] / div;
		for (int i = 0; i < ogr.size(); i++) {
			if (i != row) {
				double tmp = simplexTable[i][col];
				for (int j = 0; j < cel.length; j++) {
					simplexTable[i][j] = simplexTable[i][j]
							- (simplexTable[row][j] * tmp);
				}
				ogrValues[0][i] = ogrValues[0][i] - (ogrValues[0][row] * tmp);
			}
		}

	}

	// Bi/Aik
	public int calculateExitRow(int i) {
		int result = IMPOSSIBLE_DIV;
		double tmp = 0;
		for (int j = 0; j < ogr.size(); j++) {
			if (simplexTable[j][i] > 0) {
				ogrValues[1][j] = ogrValues[0][j] / simplexTable[j][i];
				if (result == IMPOSSIBLE_DIV) {
					result = j;
					tmp = ogrValues[1][j];
				} else if (ogrValues[1][j] < tmp) {
					result = j;
					tmp = ogrValues[1][j];
				}
			} else {
				ogrValues[1][j] = IMPOSSIBLE_DIV;
			}
		}
		return result;
	}

	public boolean isStop() {
		if (minimum) {
			for (double i : simplexCriterium) {
				if (i < 0.0)
					return false;
			}
			return true;
		} else {
			for (double i : simplexCriterium) {
				if (i > 0.0)
					return false;
			}
			return true;
		}
	}

	/**
	 * Pobieranie wartości, która najbardziej niespełnia warunku stopu
	 * 
	 * @param array
	 * @return
	 */
	public int getMaxValue(double[] array) {

		double tmp = array[0];
		int result = 0;
		for (int i = 1; i < array.length; i++) {
			if (minimum) {
				if (array[i] < tmp) {
					tmp = array[i];
					result = i;
				}
			} else {
				if (array[i] > tmp) {
					tmp = array[i];
					result = i;
				}

			}
		}
		return result;
	}

	/**
	 * Sprawdza początkowe warunki i ograniczenia
	 */
	public void checkConditions() {
		int x = 0;
		for (int i = 0; i < ogr.size(); i++) {
			double[] tmp = ogr.get(i);
			if (tmp[tmp.length - 2] == -1.99) {
				x++;
			}
		}
		// nie potrzebujemy mS
		if (x == ogr.size()) {
			double[] tmp = new double[cel.length + x];
			for (int i = 0; i < cel.length; i++) {
				tmp[i] = cel[i];
			}
			cel = tmp;
			logArea.append("\n Funkcja celu: ");
			for (int i = 0; i < cel.length; i++) {
				logArea.append(String.valueOf(cel[i]) + "x"
						+ String.valueOf(i + 1));
				if (i != cel.length - 1) {
					logArea.append(" + ");
				}
			}
			if (minimum) {
				logArea.append(" - > min");
			} else {
				logArea.append(" - > max");
			}
		} else {
			artificialVariablesCount = ogr.size() - x;
			double[] tmp = new double[cel.length + ogr.size()
					+ artificialVariablesCount];
			for (int i = 0; i < cel.length; i++) {
				tmp[i] = cel[i];
			}
			for (int i = 0; i < artificialVariablesCount; i++) {
				if (minimum) {
					tmp[i + cel.length + ogr.size()] = 1000;
				} else {
					tmp[i + cel.length + ogr.size()] = -1000;
				}

			}
			cel = tmp;
			logArea.append("\n Funkcja celu: ");
			int s = 1;
			for (int i = 0; i < cel.length; i++) {
				if (cel[i] == 1000) {
					logArea.append("mS" + String.valueOf(s));
					s++;
				} else if (cel[i] == -1000) {
					logArea.append("-mS" + String.valueOf(s));
					s++;
				} else {
					logArea.append(String.valueOf(cel[i]) + "x"
							+ String.valueOf(i + 1));
				}
				if (i != cel.length - 1) {
					logArea.append(" + ");
				}
			}
			if (minimum) {
				logArea.append(" - > min");
			} else {
				logArea.append(" - > max");
			}
		}
		// zmiana struktury funkcji ogr.
		logArea.append("\n Funkcje ograniczające: ");
		int artificalCoun = 0;
		int index = -1;
		for (int i = 0; i < ogr.size(); i++) {
			boolean isArtifical = false;
			double[] tmp = ogr.get(i);
			ogr.remove(i);
			double[] temp = new double[cel.length + 1];
			for (int j = 0; j < tmp.length; j++) {
				if (j < tmp.length - 2) {
					temp[j] = tmp[j];
				} else if (j == tmp.length - 2 && tmp[tmp.length - 2] == -1.99) {
					temp[j + i] = 1;
				} else if (j == tmp.length - 2 && tmp[tmp.length - 2] == 1.99) {
					temp[j + i] = -1;
					// zmienna sztuczna
					artificalCoun++;
					index = temp.length - 2 - artificialVariablesCount
							+ artificalCoun;
					temp[index] = 1;
					isArtifical = true;
				}
			}
			temp[temp.length - 1] = tmp[tmp.length - 1];
			logArea.append("\n " + String.valueOf(i) + ": ");
			for (int s = 0; s < temp.length; s++) {
				if (temp[s] != 0 && s != (temp.length - 1)) {
					if (isArtifical && s == index) {
						logArea.append("S" + String.valueOf(artificalCoun));
					} else {
						logArea.append(String.valueOf(temp[s]) + "x"
								+ String.valueOf(s + 1));
					}
					if (s < temp.length - 2) {
						logArea.append(" + ");
					}
				} else if (temp[s] != 0 && s == (temp.length - 1)) {
					logArea.append(" = " + String.valueOf(temp[s]));

				} else if (temp[s] == 0 && s == temp.length - 1) {
					logArea.append(" = 0.0");
				}
			}
			ogr.add(i, temp);
		}

		simplexTable = new double[ogr.size()][cel.length];
		ogrValues = new double[2][ogr.size()];
		zJ = new double[cel.length];
		simplexCriterium = new double[cel.length];
		baseValues = new double[ogr.size()][2];
		for (int i = 0; i < ogr.size(); i++) {
			for (int j = ogr.get(i).length - 1; j >= 0; j--) {
				if (ogr.get(i)[j] == 1.0) {
					baseValues[i][1] = cel[j];
					baseValues[i][0] = j;
					break;
				}
			}
		}

		for (int i = 0; i < ogr.size(); i++) {
			double[] tmp = ogr.get(i);
			for (int j = 0; j < cel.length; j++) {
				simplexTable[i][j] = tmp[j];

			}
			ogrValues[0][i] = ogr.get(i)[tmp.length - 1];
		}
	}

	public int getArtificialVariablesCount() {
		return artificialVariablesCount;
	}

	public ArrayList<double[]> getOgr() {
		return ogr;
	}

	public double[] getCel() {
		return cel;
	}

	public int getConditionNumber() {
		return conditionNumber;
	}

	public JTextArea getLogArea() {
		return logArea;
	}

	public boolean isMinimum() {
		return minimum;
	}

	public double getEndValue() {
		return endValue;
	}

	public double[][] getSimplexTable() {
		return simplexTable;
	}

	public double[] getzJ() {
		return zJ;
	}

	public double[] getSimplexCriterium() {
		return simplexCriterium;
	}

	public double[][] getBaseValues() {
		return baseValues;
	}

	public double[][] getOgrValues() {
		return ogrValues;
	}

	public int[] getExitCell() {
		return exitCell;
	}

	public String[] getCelNames() {
		return celNames;
	}

	public void setCelNames(String celName, int i) {
		this.celNames[i] = celName;
	}

}
